{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Creating a custom fluid using GenericPhase"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OpenPNM comes with a small selection of pre-written phases (Air, Water, Mercury).  In many cases users will want different options but it is not feasible or productive to include a wide variety of fluids.  Consequntly OpenPNM has a mechanism for creating custom phases for this scneario.  This requires that the user have correlations for the properties of interest, such as the viscosity as a function of temperature in the form of a polynomial for instance.  This is process is described in the following tutuorial:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Import the usual packages and instantiate a small network for demonstration purposes:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:51.560233Z",
     "iopub.status.busy": "2021-06-24T11:32:51.558595Z",
     "iopub.status.idle": "2021-06-24T11:32:52.124534Z",
     "shell.execute_reply": "2021-06-24T11:32:52.123030Z"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import openpnm as op"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.134874Z",
     "iopub.status.busy": "2021-06-24T11:32:52.133454Z",
     "iopub.status.idle": "2021-06-24T11:32:52.141939Z",
     "shell.execute_reply": "2021-06-24T11:32:52.140680Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "openpnm.network.Cubic : net_01\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "#     Properties                                    Valid Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1     pore.coords                                      27 / 27   \n",
      "2     throat.conns                                     54 / 54   \n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "#     Labels                                        Assigned Locations\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1     pore.all                                      27        \n",
      "2     pore.back                                     9         \n",
      "3     pore.bottom                                   9         \n",
      "4     pore.front                                    9         \n",
      "5     pore.internal                                 27        \n",
      "6     pore.left                                     9         \n",
      "7     pore.right                                    9         \n",
      "8     pore.surface                                  26        \n",
      "9     pore.top                                      9         \n",
      "10    throat.all                                    54        \n",
      "11    throat.internal                               54        \n",
      "12    throat.surface                                48        \n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "pn = op.network.Cubic(shape=[3, 3, 3], spacing=1e-4)\n",
    "print(pn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now that a network is defined, we can create a `GenericPhase` object associated with it.  For this demo we'll make an oil phase, so let's call it `oil`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.149531Z",
     "iopub.status.busy": "2021-06-24T11:32:52.148290Z",
     "iopub.status.idle": "2021-06-24T11:32:52.152619Z",
     "shell.execute_reply": "2021-06-24T11:32:52.153620Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "openpnm.phases.GenericPhase : phase_01\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "#     Properties                                    Valid Values\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1     pore.pressure                                    27 / 27   \n",
      "2     pore.temperature                                 27 / 27   \n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "#     Labels                                        Assigned Locations\n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1     pore.all                                      27        \n",
      "2     throat.all                                    54        \n",
      "――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "oil = op.phases.GenericPhase(network=pn)\n",
    "print(oil)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As can be seen in the above printout, this phase has a temperature and pressure set at all locations, but has no other physical properties.  \n",
    "\n",
    "There are 2 ways add physical properties. They can be hard-coded, or added as a 'pore-scale model'.  \n",
    "- Some are suitable as hard coded values, such as molecular mass\n",
    "- Others should be added as a model, such as viscosity, which is a function of temperature so could vary spatially and should be updated depending on changing conditions in the simulation."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Start with hard-coding:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.161296Z",
     "iopub.status.busy": "2021-06-24T11:32:52.160333Z",
     "iopub.status.idle": "2021-06-24T11:32:52.164209Z",
     "shell.execute_reply": "2021-06-24T11:32:52.164952Z"
    }
   },
   "outputs": [],
   "source": [
    "oil['pore.molecular_mass'] = 100.0  # g/mol"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.173068Z",
     "iopub.status.busy": "2021-06-24T11:32:52.172048Z",
     "iopub.status.idle": "2021-06-24T11:32:52.176382Z",
     "shell.execute_reply": "2021-06-24T11:32:52.177181Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[100. 100. 100. 100. 100. 100. 100. 100. 100. 100. 100. 100. 100. 100.\n",
      " 100. 100. 100. 100. 100. 100. 100. 100. 100. 100. 100. 100. 100.]\n"
     ]
    }
   ],
   "source": [
    "print(oil['pore.molecular_mass'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As can be seen, this puts the value of 100.0 g/mol in every pore.  Note that you could also assign each pore explicitly with a numpy array.  OpenPNM automatically assigns a scalar value to every location as shown above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.185742Z",
     "iopub.status.busy": "2021-06-24T11:32:52.184630Z",
     "iopub.status.idle": "2021-06-24T11:32:52.188317Z",
     "shell.execute_reply": "2021-06-24T11:32:52.189392Z"
    }
   },
   "outputs": [],
   "source": [
    "oil['pore.molecular_mass'] = np.ones(shape=[pn.Np, ])*120.0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.198231Z",
     "iopub.status.busy": "2021-06-24T11:32:52.196821Z",
     "iopub.status.idle": "2021-06-24T11:32:52.201539Z",
     "shell.execute_reply": "2021-06-24T11:32:52.202661Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[120. 120. 120. 120. 120. 120. 120. 120. 120. 120. 120. 120. 120. 120.\n",
      " 120. 120. 120. 120. 120. 120. 120. 120. 120. 120. 120. 120. 120.]\n"
     ]
    }
   ],
   "source": [
    "print(oil['pore.molecular_mass'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can also specify something like viscosity this way as well, but it's not recommended:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.211289Z",
     "iopub.status.busy": "2021-06-24T11:32:52.210005Z",
     "iopub.status.idle": "2021-06-24T11:32:52.213848Z",
     "shell.execute_reply": "2021-06-24T11:32:52.214820Z"
    }
   },
   "outputs": [],
   "source": [
    "oil['pore.viscosity'] = 1600.0  # cP"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The problem with specifying the viscosity as a hard-coded value is that viscosity is a function of temperature (among other things), so if we adjust the temperature on the `oil` object it will have no effect on the hard-coded viscosity:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.224238Z",
     "iopub.status.busy": "2021-06-24T11:32:52.223089Z",
     "iopub.status.idle": "2021-06-24T11:32:52.227814Z",
     "shell.execute_reply": "2021-06-24T11:32:52.228690Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600.\n",
      " 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600. 1600.\n",
      " 1600. 1600. 1600.]\n"
     ]
    }
   ],
   "source": [
    "oil['pore.temperature'] = 100.0  # C\n",
    "print(oil['pore.viscosity'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The correct way to specify something like viscosity is to use pore-scale models.  There is a large libary of pre-written models in the `openpnm.models` submodule.  For instance, a polynomial can be used as follows:\n",
    "\n",
    "$$ viscosity = a_0 + a_1 \\cdot T + a_2 \\cdot T^2 = 1600 + 12 T - 0.05 T^2$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.239458Z",
     "iopub.status.busy": "2021-06-24T11:32:52.238032Z",
     "iopub.status.idle": "2021-06-24T11:32:52.241767Z",
     "shell.execute_reply": "2021-06-24T11:32:52.242840Z"
    }
   },
   "outputs": [],
   "source": [
    "mod = op.models.misc.polynomial\n",
    "oil.add_model(propname='pore.viscosity', model=mod, \n",
    "            a=[1600, 12, -0.05], prop='pore.temperature')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can now see that our previously written values of viscosity (1600.0) have been overwritten by the values coming from the model:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.252544Z",
     "iopub.status.busy": "2021-06-24T11:32:52.251117Z",
     "iopub.status.idle": "2021-06-24T11:32:52.255867Z",
     "shell.execute_reply": "2021-06-24T11:32:52.256990Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300.\n",
      " 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300. 2300.\n",
      " 2300. 2300. 2300.]\n"
     ]
    }
   ],
   "source": [
    "print(oil['pore.viscosity'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And moreover, if we change the temperature the model will update the viscosity values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.265750Z",
     "iopub.status.busy": "2021-06-24T11:32:52.264590Z",
     "iopub.status.idle": "2021-06-24T11:32:52.349349Z",
     "shell.execute_reply": "2021-06-24T11:32:52.350490Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000.\n",
      " 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000. 2000.\n",
      " 2000. 2000. 2000.]\n"
     ]
    }
   ],
   "source": [
    "oil['pore.temperature'] = 40.0  # C\n",
    "oil.regenerate_models()\n",
    "print(oil['pore.viscosity'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note the call to `regenerate_models`, which is necessary to actually re-run the model using the new temperature."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When a pore-scale model is added to an object, it is stored under the `models` attribute, which is a dictionary with names corresponding the property that is being calculated (i.e. 'pore.viscosity'):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.357147Z",
     "iopub.status.busy": "2021-06-24T11:32:52.355738Z",
     "iopub.status.idle": "2021-06-24T11:32:52.361172Z",
     "shell.execute_reply": "2021-06-24T11:32:52.360181Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "#   Property Name                       Parameter                 Value\n",
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n",
      "1   pore.viscosity                      model:                    polynomial\n",
      "                                        a:                        [1600, 12, -0.05]\n",
      "                                        prop:                     pore.temperature\n",
      "                                        regeneration mode:        normal\n",
      "―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――\n"
     ]
    }
   ],
   "source": [
    "print(oil.models)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can reach into this dictionary and alter the parameters of the model if necessary:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "execution": {
     "iopub.execute_input": "2021-06-24T11:32:52.368593Z",
     "iopub.status.busy": "2021-06-24T11:32:52.367475Z",
     "iopub.status.idle": "2021-06-24T11:32:52.371240Z",
     "shell.execute_reply": "2021-06-24T11:32:52.372166Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568.\n",
      " 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568. 1568.\n",
      " 1568. 1568. 1568.]\n"
     ]
    }
   ],
   "source": [
    "oil.models['pore.viscosity']['a'] = [1200, 10, -0.02]\n",
    "oil.regenerate_models()\n",
    "print(oil['pore.viscosity'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `models` submodule has a variety of common functions, stored under `models.misc.basic_math` or `models.misc.common_funtions`.  There are also some models specific to physical properties under `models.phases`."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
